<?php

namespace Fluency;

/*
 * @package Fluency
 * @author  Ignas Rudaitis
 * @version 2.0.0
 */
class Executor
{
    private $actionLoader;
    private $actions = array();
    private $metadata = array();
    private $argumentMappings = array();

    public function __construct($actionLoader)
    {
        $this->actionLoader = $actionLoader;
    }

    private function getAction($name)
    {
        $action = &$this->actions[$name];
        if (!isset($action)) {
            try {
                $result = call_user_func($this->actionLoader, $name);
                $this->validateLoaderResult($result);
                list($action, $this->argumentMappings[$name], $this->metadata[$name]) = $result;
            } catch (\InvalidArgumentException $e) {
                $action = false;
            }
        }
        return $action;
    }

    private function validateLoaderResult($result)
    {
        $error = $this->getInvalidLoaderResultErrorMessage($result);
        if ($error !== null) {
            throw new \UnexpectedValueException($error);
        }
    }

    private function getInvalidLoaderResultErrorMessage($result)
    {
        if (!is_array($result)) {
            return "The action loader did not return an array";
        } elseif (!is_callable(array_shift($result))) {
            return "The first element of the action loader result is not callable";
        } elseif (!is_array(array_shift($result))) {
            return "The second element of the action loader result is not an array";
        } elseif (!array_shift($result) instanceof Metadata\ActionMetadata) {
            return "The third element of the action loader result is not an instance of " .
                   'Fluency\Metadata\ActionMetadata';
        }
    }

    /**
     * @return boolean
     */
    public function actionExists($name)
    {
        return $this->getAction($name) !== false;
    }

    /**
     * @return Fluency\Metadata\ActionMetadata
     */
    public function getActionMetadata($actionName)
    {
        $this->ensureActionExists($actionName);
        return $this->metadata[$actionName];
    }

    /**
     * @return mixed[]
     */
    public function executeAction($name, Injector $injector)
    {
        $this->ensureActionExists($name);
        $result = $injector->call($this->actions[$name], $this->argumentMappings[$name]);
        if (!is_array($result)) {
            throw new \UnexpectedValueException("Action '$name' did not return an array");
        }
        return $result;
    }

    private function ensureActionExists($name)
    {
        if ($this->getAction($name) === false) {
            throw new \InvalidArgumentException("Action '$name' does not exist");
        }
    }
}
